package Rangel.firebrigade.world;

import Rangel.common.world.helper.PropertyHelper;
import Rangel.common.world.helper.RangelWorldHelper;
import Rangel.common.world.helper.RoadHelper;
import adf.agent.communication.MessageManager;
import adf.agent.develop.DevelopData;
import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import adf.agent.module.ModuleManager;
import adf.agent.precompute.PrecomputeData;
import Rangel.common.RangelConstants;
import Rangel.firebrigade.cluster.RangelFireClustering;
import Rangel.common.world.entity.RangelBuilding;
import Rangel.common.world.entity.RangelRoad;
import rescuecore2.config.NoSuchConfigOptionException;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.FireBrigade;
import rescuecore2.standard.entities.Hydrant;
import rescuecore2.standard.entities.StandardEntity;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RangelFireBrigadeWorld extends RangelWorldHelper {
    protected RoadHelper roadHelper;
    protected Simulator simulator;
    private int maxWater;
    private int waterRefillRate;
    private String fileName;
    private RangelFireClustering fireClustering;


    public RangelFireBrigadeWorld(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) {
        super(agentInfo, worldInfo, scenarioInfo, moduleManager, developData);
        fileName = getMapName() + ".rays";
    }

    private void prepareFirebrigadeWorld(ScenarioInfo scenarioInfo) {
        roadHelper = getHelper(RoadHelper.class);
        initSimulator();

        setMaxWater(scenarioInfo.getFireTankMaximum());
        int refugeRefillRateTemp = RangelConstants.WATER_REFILL_RATE;
        try {
            refugeRefillRateTemp = scenarioInfo.getRawConfig().getIntValue(RangelConstants.WATER_REFILL_RATE_KEY);
            isWaterRefillRateInRefugeSet = true;
        } catch (NoSuchConfigOptionException ignored) {
            isWaterRefillRateInRefugeSet = false;
        }
        setWaterRefillRate(refugeRefillRateTemp);
        try {
            isWaterRefillRateInHydrantSet = true;
        } catch (NoSuchConfigOptionException ignored) {
            isWaterRefillRateInHydrantSet = false;
        }
        setBorderBuildings();
    }


    @Override
    public RangelWorldHelper precompute(PrecomputeData precomputeData) {
        super.precompute(precomputeData);
        if (this.getCountPrecompute() >= 2) {
            return this;
        }

        fireClustering = new RangelFireClustering(agentInfo, worldInfo, scenarioInfo, moduleManager, developData);

        try {
            createCND(PrecomputeData.PRECOMP_DATA_DIR.getAbsolutePath() + File.separator + fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return this;
    }

    @Override
    public RangelWorldHelper resume(PrecomputeData precomputeData) {//procumputer data resume
        super.resume(precomputeData);
        if (this.getCountResume() >= 2) {
            return this;
        }
        fireClustering = new RangelFireClustering(agentInfo, worldInfo, scenarioInfo, moduleManager, developData);

        readCND(PrecomputeData.PRECOMP_DATA_DIR.getAbsolutePath() + File.separator + fileName);

        prepareFirebrigadeWorld(scenarioInfo);

        return this;
    }

    @Override
    public RangelWorldHelper preparate() {//nonprecomputer
        super.preparate();
        if (this.getCountPreparate() >= 2) {
            return this;
        }
        fireClustering = new RangelFireClustering(agentInfo, worldInfo, scenarioInfo, moduleManager, developData);

        readCND(PrecomputeData.PRECOMP_DATA_DIR.getAbsolutePath() + File.separator + fileName);

        prepareFirebrigadeWorld(scenarioInfo);

        return this;
    }


    private void initSimulator() {
        threadPool.submit(() -> {
            simulator = new Simulator(RangelFireBrigadeWorld.this);
        });
    }

    private int lastUpdateTime = 0;

    @Override
    public RangelWorldHelper updateInfo(MessageManager messageManager) {
        if (lastUpdateTime >= getTime()) {
            return this;
        }
        updateBeforeSense();
        super.updateInfo(messageManager);
        updateAfterSense();
        lastUpdateTime = getTime();
        fireClustering.updateInfo(messageManager);
        return this;
    }

    public void updateBeforeSense() {
        if (simulator != null) {
            simulator.update();
        }
    }

    public void updateAfterSense() {

        estimatedBurningBuildings.clear();
        for (RangelBuilding RangelBuilding : getRangelBuildings()) {
            if (RangelBuilding.getEstimatedFieryness() >= 1 && RangelBuilding.getEstimatedFieryness() <= 3) {
                estimatedBurningBuildings.add(RangelBuilding);
            }
        }

    }

    private void readCND(String fileName) {
        File f = new File(fileName);
        if (!f.exists() || !f.canRead()) {
            createCND();
            return;
        }
        try {
            BufferedReader br = new BufferedReader(new FileReader(f));
            String nl;
            while (null != (nl = br.readLine())) {
                int x = Integer.parseInt(nl);
                int y = Integer.parseInt(br.readLine());
                int quantity = Integer.parseInt(br.readLine());
                double hitRate = Double.parseDouble(br.readLine());
                List<RangelBuilding> bl = new ArrayList<>();
                List<Float> weight = new ArrayList<>();
                for (int c = 0; c < quantity; c++) {
                    int ox = Integer.parseInt(br.readLine());
                    int oy = Integer.parseInt(br.readLine());
                    Building building = getBuildingInPoint(ox, oy);
                    if (building == null) {
                        System.err.println("building not found: " + ox + "," + oy);
                        br.readLine();
                    } else {
                        bl.add(getRangelBuilding(building.getID()));
                        weight.add(Float.parseFloat(br.readLine()));
                    }

                }
                Building b = getBuildingInPoint(x, y);
                RangelBuilding building = getRangelBuilding(b.getID());
                building.setConnectedBuilding(bl);
                building.setConnectedValues(weight);
                building.setHitRate(hitRate);
            }
            br.close();
            if (RangelConstants.DEBUG_FIRE_BRIGADE) {
                System.out.println("Read from file:" + fileName);
            }
        } catch (IOException ex) {
            createCND();
            ex.printStackTrace();
        }
    }

    private void createCND() {
        getRangelBuildings().parallelStream().forEach(RangelB -> {
            RangelB.initWallValues();
            RangelB.cleanup();
        });
    }

    private void createCND(String fileName) throws IOException {
        createCND();
        File f = new File(fileName);

        f.delete();
        f.createNewFile();

        final BufferedWriter bw = new BufferedWriter(new FileWriter(f));
        getRangelBuildings().forEach(RangelB -> {
            try {
                bw.write(RangelB.getSelfBuilding().getX() + "\n");
                bw.write(RangelB.getSelfBuilding().getY() + "\n");
                bw.write(RangelB.getConnectedBuilding().size() + "\n");
                bw.write(RangelB.getHitRate() + "\n");

                for (int c = 0; c < RangelB.getConnectedBuilding().size(); c++) {
                    RangelBuilding building = RangelB.getConnectedBuilding().get(c);
                    Float val = RangelB.getConnectedValues().get(c);
                    bw.write(building.getSelfBuilding().getX() + "\n");
                    bw.write(building.getSelfBuilding().getY() + "\n");
                    bw.write(val + "\n");
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }

        });

        bw.close();
    }

    private ExecutorService threadPool = Executors.newCachedThreadPool();

    public void setBorderBuildings() {
        threadPool.submit(() -> {
            borderBuildings = borderFinder.getBordersOf(0.9);
        });

    }


    public int getMaxWater() {
        return maxWater;
    }

    public void setMaxWater(int maxWater) {
        this.maxWater = maxWater;
    }

    public int getWaterRefillRate() {
        return waterRefillRate;
    }

    public void setWaterRefillRate(int waterRefillRate) {
        this.waterRefillRate = waterRefillRate;
    }

    public int getMaxPower() {
        return scenarioInfo.getFireExtinguishMaxSum();
    }


    public RangelFireClustering getFireClustering() {
        return fireClustering;
    }

    private int lastUpdateHydrants = -1;

    public Set<StandardEntity> getAvailableHydrants() {
        Set<StandardEntity> availableHydrants = new HashSet<>();
        if (lastUpdateHydrants < getTime() && selfHuman instanceof FireBrigade && !getHydrants().isEmpty()) {
            lastUpdateHydrants = getTime();
            availableHydrants.addAll(getHydrants());
            StandardEntity position;
            RangelRoad hydrantRangelRoad;
            PropertyHelper propertyHelper = getHelper(PropertyHelper.class);
            for (StandardEntity fireBrigadeEntity : fireBrigades) {
                if (fireBrigadeEntity.getID().equals(selfHuman.getID())) {
                    continue;
                }
                FireBrigade fireBrigade = (FireBrigade) fireBrigadeEntity;
                if (fireBrigade.isPositionDefined()) {
                    position = worldInfo.getPosition(fireBrigade);
                    if (position instanceof Hydrant) {
                        hydrantRangelRoad = getRangelRoad(position.getID());
                        int agentDataTime = propertyHelper.getEntityLastUpdateTime(fireBrigade);
                        int hydrantSeenTime = hydrantRangelRoad.getLastSeenTime();
                        if (getTime() - agentDataTime > 10 && getTime() - hydrantSeenTime > 10) {
                            continue;
                        }
                        availableHydrants.remove(position);
                    }
                }
            }
        }

        return availableHydrants;
    }
}
